package br.com.merks.service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.apache.log4j.Logger;

import br.com.caelum.vraptor.ioc.Component;
import br.com.caelum.vraptor.util.jpa.EntityManagerCreator;
import br.com.merks.controller.vo.MarketInfoVO;
import br.com.merks.controller.vo.TradeGraphVO;
import br.com.merks.controller.vo.TradeSumVO;
import br.com.merks.model.Category;
import br.com.merks.model.Market;
import br.com.merks.model.MarketAnswer;
import br.com.merks.model.MarketComment;
import br.com.merks.model.Trade;
import br.com.merks.model.User;
import br.com.merks.service.exception.InsufficientFundsException;
import br.com.merks.service.exception.InsufficientMerksException;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.KeyFactory;

@Component
public class TradeServiceImpl implements TradeService {
	public static final Logger logger = Logger.getLogger(TradeServiceImpl.class);
	/* Componentes */
	private EntityManager entityManager;
	

	private MailService mailService;
	private TradeAlgorithm tradeAlgorithm;
	/* Variaveis */
	
	
	/* Construtores */
	public TradeServiceImpl(EntityManagerCreator emCreator, MailService mailService, TradeAlgorithm tradeAlgorithm) {
		this.entityManager = emCreator.getInstance();
		this.mailService = mailService;
		this.tradeAlgorithm = tradeAlgorithm;
	}
	public BigDecimal getCurrentPrice(Market market, MarketAnswer answer) {
		logger.debug("Getting after price...");
		List<Trade>	trades = this.entityManager.createNamedQuery("trade.findByMarket").setParameter("marketId",market.getId()).getResultList();
		return this.tradeAlgorithm.getPrice(answer.getId(),0, this.getTotalMerks(trades, answer.getId()));
	}
	public BigDecimal getAfterPrice(Market market, MarketAnswer answer,Trade tradeInput) {
		logger.debug("Getting after price...");
		List<Trade>	trades = this.entityManager.createNamedQuery("trade.findByMarket").setParameter("marketId",market.getId()).getResultList();
		return this.tradeAlgorithm.getPrice(answer.getId(),tradeInput.getValue(), this.getTotalMerks(trades, answer.getId()));
	}
	
	public BigDecimal getTotalCost(Market market, MarketAnswer answer,Trade tradeInput) {
		logger.debug("Getting after price...");
		List<Trade>	trades = this.entityManager.createNamedQuery("trade.findByMarket").setParameter("marketId",market.getId()).getResultList();
		return this.tradeAlgorithm.getCostPrice(answer.getId(),tradeInput.getValue(), this.getTotalMerks(trades, answer.getId()));

	}
	public Trade buy(Trade trade, User user) throws InsufficientFundsException {
		
		List<Trade>	trades = this.entityManager.createNamedQuery("trade.findByMarket").setParameter("marketId",trade.getMarketId()).getResultList();
		
		trade.setTransactionPrice(
				this.tradeAlgorithm.getCostPrice(trade.getMarketAnswerId(),trade.getValue(), this.getTotalMerks(trades, trade.getMarketAnswerId()))
				);
		
		trade.setMerkPrice(
				this.tradeAlgorithm.getPrice(trade.getMarketAnswerId(),0, this.getTotalMerks(trades, trade.getMarketAnswerId()))
				);
		
		if ((user.getMerks().subtract(trade.getTransactionPrice())).signum() == -1 ) throw new InsufficientFundsException();
		
		this.entityManager.getTransaction().begin();
		this.entityManager.persist(trade);
		this.entityManager.getTransaction().commit();
		return trade;
	}
	public Trade sell(Trade trade, User user) throws InsufficientMerksException {

		List<Trade>	trades = this.entityManager.createNamedQuery("trade.findByMarket").setParameter("marketId",trade.getMarketId()).getResultList();
		
		Map<Long,Integer> totalMerksMap = this.getTotalMerks(trades, trade.getMarketAnswerId());
		
		System.out.println("Size " + totalMerksMap.size());
		int value = totalMerksMap.get(trade.getMarketAnswerId());
		if (value < trade.getValue()) throw new InsufficientMerksException();
		
		trade.setTransactionPrice(
				this.tradeAlgorithm.getCostPrice(trade.getMarketAnswerId(),trade.getValue(), totalMerksMap)
				);
		
		trade.setMerkPrice(
				this.tradeAlgorithm.getPrice(trade.getMarketAnswerId(),0, totalMerksMap)
				);
		
		
		
		this.entityManager.getTransaction().begin();
		this.entityManager.persist(trade);
		this.entityManager.getTransaction().commit();
		return trade;
	}
	
	public List<TradeGraphVO> tradeHistory(Trade trade) {
		Query q = this.entityManager.createNamedQuery("trade.findByMarket");
		q.setParameter("marketId",trade.getMarketId());
		List<Trade>	trades = q.getResultList();
		//System.out.println("Tamanho : " + trades.size());
		HashMap<Long,TradeGraphVO> bag = new HashMap<Long,TradeGraphVO>();
		int maxItems = 0;
		//Extrai a lista para um map separado por resposta
		for (Trade t : trades) {
			TradeGraphVO extractTrade = bag.get(t.getMarketAnswerId());
			if ((extractTrade==null)) {
				
				TradeGraphVO tgvo = new TradeGraphVO();
				tgvo.getTrades().add(t);
				tgvo.setMarketAnswerId(t.getMarketAnswerId());
				bag.put(t.getMarketAnswerId(), tgvo);
				if (maxItems<tgvo.getTrades().size()) maxItems = tgvo.getTrades().size();
			} else {
				extractTrade.getTrades().add(t);
				bag.put(t.getMarketAnswerId(), extractTrade);
				if (maxItems<extractTrade.getTrades().size()) maxItems = extractTrade.getTrades().size();
			}
		}
		//System.out.println("Tamanho do bag : " + bag.size());
		//extrai os 10 últimos resultados
		List<TradeGraphVO> result = new ArrayList<TradeGraphVO>();
		for (Long marketAnswerId : bag.keySet()) {

			TradeGraphVO extractTrade = bag.get(marketAnswerId);
			int size = extractTrade.getTrades().size();
			//System.out.println("Tamanho do size : " + bag.size() + " de " + marketAnswerId);
			int plus = 0; 
			if (size>10) { plus = size-10; }
			List<Trade> last = new ArrayList<Trade>();	
			for (int x = 0+plus; x < size; x++) {
				System.out.println(extractTrade.getTrades().get(x).getId() + " " + extractTrade.getTrades().get(x).getMerkPrice());
				last.add(extractTrade.getTrades().get(x));
			}
			//preenche com o último resultado caso a resposta possua menos de 10 resultados
			int tradesSize = last.size();
			if (tradesSize<10) {
				for (int x = 0; x<10-tradesSize;x++) {
					last.add(last.get(tradesSize-1));
				}
			}
			extractTrade.setTrades(last);
			result.add(extractTrade);
		}
		return result;
	}

	
	public List<Trade> listTransactions(Market market, User user) {
		
		List<Trade>	trades = this.entityManager.createNamedQuery("trade.findByMarketAndUser").setParameter("marketId",market.getId()).setParameter("userId",user.getId()).getResultList();
		
		return trades;
		
	}
	
	public MarketInfoVO getMarketUserTradeInfo(Market market, User user) {
		
		MarketInfoVO ret = new MarketInfoVO();
		ret.setTotalMerks(0);
		ret.setTotalPrice(new BigDecimal(0));
		List<Trade>	trades = this.entityManager.createNamedQuery("trade.findByMarketAndUser").setParameter("marketId",market.getId()).setParameter("userId",user.getId()).getResultList();
		
		
		HashMap<Long,TradeSumVO> bag = new HashMap<Long,TradeSumVO>();
		for (Trade t : trades) {
			TradeSumVO extractTrade = bag.get(t.getMarketAnswerId());
			if ((extractTrade==null)) {
				TradeSumVO tsvo = new TradeSumVO();
				tsvo.setAnswerTotalPrice(t.getTransactionPrice());
				tsvo.setUserId(user.getId());
				tsvo.setMarketId(market.getId());
				tsvo.setTotalMerks(t.getValue());
				tsvo.setMarketAnswerId(t.getMarketAnswerId());
				bag.put(t.getMarketAnswerId(), tsvo);
			} else {
				extractTrade.setAnswerTotalPrice(extractTrade.getAnswerTotalPrice().add(t.getTransactionPrice()));
				extractTrade.setTotalMerks(extractTrade.getTotalMerks()+t.getValue());
				bag.put(extractTrade.getMarketAnswerId(), extractTrade);
			}
		}
		List<TradeSumVO> result = new ArrayList<TradeSumVO>();
		for (Long marketAnswerId : bag.keySet()) {
			TradeSumVO extractTrade = bag.get(marketAnswerId);
			extractTrade.setActualAnswerTotalPrice(
					this.tradeAlgorithm.getPrice(marketAnswerId,0, this.getTotalMerks(trades, marketAnswerId)).multiply(new BigDecimal(extractTrade.getTotalMerks()))
					);
			result.add(extractTrade);
			ret.setTotalMerks(ret.getTotalMerks()+extractTrade.getTotalMerks());
			ret.setTotalPrice(ret.getTotalPrice().add(extractTrade.getAnswerTotalPrice()));
		}
		Collections.sort(result,
				new Comparator<TradeSumVO>() { 
				 public int compare(TradeSumVO first, TradeSumVO other) {  
        			if (first.getMarketAnswerId() < other.getMarketAnswerId()) {
        				return -1;
					}
					if (first.getMarketAnswerId() > other.getMarketAnswerId()) {
						return 1;
					} 
					else {
						return 0;
					}
			    }
			});
		ret.setListOfSumOfAnswersTrades(result);
		return ret;
		
	}
	
	public static Map<Long,Integer> getTotalMerks(List<Trade> trades, Long answerId) {
		int merks = 0;
		HashMap<Long,Integer> bag = new HashMap<Long,Integer>();
		for (Trade t : trades) {
					Integer sum = bag.get(t.getMarketAnswerId());
					if (sum==null) sum = 0;
					sum = sum + t.getValue();
					bag.put(t.getMarketAnswerId(), sum);
			}
		return bag;
	}

	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}	
}

